﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Xml;

namespace Fadd.Logging.Targets
{
    /// <summary>
    /// Writes log entries to files.
    /// </summary>
    public class FileTarget : ITarget
    {
		private TargetConfiguration _config;
		private readonly Queue<LogEntry> _queue = new Queue<LogEntry>();
		private bool _isWriting;
    	private string _path;

		/// <summary>
		/// Initializes a new instance of the <see cref="FileTarget"/> class.
		/// </summary>
		/// <param name="formatter">The formatter.</param>
		/// <exception cref="ArgumentNullException"><c>formatter</c> is null.</exception>
        public FileTarget(IFormatter formatter)
        {
			if (formatter == null)
				throw new ArgumentNullException("formatter");

            Formatter = formatter;
			EnsureDirectory(DateTime.Now);
			DaysToKeepLogs = 7;
			BaseDirectory = string.Empty;
        }


        /// <summary>
        /// Gets or sets file name.
        /// </summary>
        /// <remarks>
        /// </remarks>
        public string FileName { get; set; }

    	/// <summary>
    	/// Gets or sets number of days to keep logs.
    	/// </summary>
    	public int DaysToKeepLogs { get; set; }

    	/// <summary>
    	/// Gets or sets don't queue log entries in the thread pool, write them directly.
    	/// </summary>
    	public bool WriteDirectly { get; set; }

    	private string _baseDirectory;

    	/// <summary>
    	/// Gets or sets base directory that logs are written to.
    	/// </summary>
    	public string BaseDirectory
    	{
    		get { return _baseDirectory; }
    		set
    		{
				if (string.IsNullOrEmpty(value))
					value = string.Empty;
				else if (value[value.Length - 1] != '\\')
					value += "\\";

    			_baseDirectory = value;
    		}
    	}

    	/// <summary>
    	/// Load configuration.
    	/// </summary>
    	/// <param name="configuration">Configuration to load.</param>
    	/// <exception cref="InvalidOperationException">If configuration has already been loaded.</exception>
    	/// <exception cref="ArgumentNullException"><c>configuration</c> is null.</exception>
		public void LoadConfiguration(TargetConfiguration configuration)
    	{
    		if (configuration == null)
				throw new ArgumentNullException("configuration");


			_config = configuration;
    		Name = _config.Name;
			foreach (XmlNode node in configuration.ChildConfiguration)
			{
				try
				{
					if (node.ChildNodes.Count != 1)
						throw new ConfigurationErrorsException("Expected FileTarget, child element " + node.Name + " to have a single value.");

					switch (node.Name)
					{
						case "DaysToKeepLogs":
							DaysToKeepLogs = int.Parse(node.FirstChild.Value);
							break;
						case "BaseDirectory":
							BaseDirectory = node.FirstChild.Value;
							break;
						case "WriteDirectly":
							WriteDirectly = node.FirstChild.Value.ToLower() == "true";
							break;
					}
				}
				catch (FormatException err)
				{
					throw new ConfigurationErrorsException("Failed to parse color value '" + node.FirstChild.Value + "'.", err);
				}
			}

			try
			{
				if (!string.IsNullOrEmpty(BaseDirectory))
					CreateDirectories(BaseDirectory);
			}
			catch(Exception err)
			{
				LogManager.TriggerEvent(this, err);
			}
    	}

    	/// <summary>
        /// Gets or sets target name.
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Gets or sets formatter
        /// </summary>
        public IFormatter Formatter { get; set; }


        private void EnsureDirectory(DateTime dateTime)
        {
			_path = BaseDirectory + Formatter.FormatDate(dateTime);
			try
			{
				if (!Directory.Exists(_path))
					Directory.CreateDirectory(_path);
			}
			catch(Exception err)
			{
				LogManager.TriggerEvent(this, err);
			}
        }

		/// <summary>
		/// Write a log entry in the target.
		/// </summary>
		/// <param name="logEntry">entry to write</param>
		public void Write(LogEntry logEntry)
		{
			if (WriteDirectly)
			{
				lock (_queue)
					WriteEntry(logEntry);
				return;
			}

			lock (_queue)
			{
				_queue.Enqueue(logEntry);
				if (_isWriting) return;

				System.Threading.ThreadPool.QueueUserWorkItem(WriteLogEntry);
				_isWriting = true;
			}
		}

		private void CreateDirectories(string path)
		{
			string[] parts = path.Split('\\');
			string fullPath = string.Empty;
			foreach (var part in parts)
			{
				fullPath += part + "\\";
				if (!Directory.Exists(fullPath))
					Directory.CreateDirectory(fullPath);
			}
			
		}
		private void WriteLogEntry(object state)
		{
			LogEntry entry;
			lock (_queue)
				entry = _queue.Dequeue();

			WriteEntry(entry);

			lock (_queue)
			{
				if (_queue.Count == 0)
				{
					_isWriting = false;
					return;
				}
			}

			WriteLogEntry(null);
		}

    	private void WriteEntry(LogEntry entry)
    	{
    		try
    		{
    			EnsureDirectory(entry.CreatedAt);
    			string msg = Formatter.Format(entry);
    			File.AppendAllText(_path + '\\' + entry.Logger.Name + ".log", msg + Environment.NewLine);
    		}
    		catch (Exception err)
    		{
    			LogManager.TriggerEvent(this, err);
    		}
    	}
    }
}
